package crm.base.interceptor;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;

import crm.base.dialect.DatabaseDialects;
import crm.base.dialect.Dialect;
import crm.base.dialect.SQLServerDialect;
import crm.base.model.Page;

/**
 * 
 * mybatis 分页拦截器
 * 
 * @author Zhang Qiongbiao
 * 
 */
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class PaginationInterceptor implements Interceptor {
	
	private String dialect;

	public static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
	public static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		
		StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
		MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
		
		RowBounds rowBounds = (RowBounds) metaStatementHandler.getValue("delegate.rowBounds");
		if (rowBounds == null || rowBounds == RowBounds.DEFAULT) {
			return invocation.proceed();
		}
		
		DatabaseDialects databaseType = DatabaseDialects.getDatabaseDialect(dialect);
		if (databaseType == null) {
			throw new RuntimeException("the value of the dialect property is not defined : " + dialect);
		}
		Dialect dialect = null;
		switch (databaseType) {
			case SQLSERVER:
				dialect = new SQLServerDialect();
				break;
			case ORACLE:
				break;
			case MYSQL:
				break;
			default:
				break;
		}
		
		BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
		if (rowBounds instanceof Page) {
			Page page = (Page) rowBounds;
			dialect.init(boundSql.getSql(), page.getFirst(), page.getPageSize());
			if (!page.isAutoCount()) {
				Connection conn = (Connection) invocation.getArgs()[0];
				page.setTotalCount(getTotalCount(conn, dialect.getTotalCountString(), metaStatementHandler, boundSql));
			}
		} else {
			dialect.init(boundSql.getSql(), rowBounds.getOffset(), rowBounds.getLimit());
		}

		metaStatementHandler.setValue("delegate.boundSql.sql", dialect.getLimitString());
		metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
		metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
		
		return invocation.proceed();
	}
	
	private long getTotalCount(Connection conn, String sql, MetaObject metaStatementHandler, BoundSql boundSql) throws Exception {
		long totalCount = 0;
		PreparedStatement stmt = null;
		ResultSet rs = null;

		try {
			Configuration configuration = (Configuration) metaStatementHandler.getValue("delegate.configuration");
			MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
			
			BoundSql countBoundSql = new BoundSql(configuration, sql, boundSql.getParameterMappings(), boundSql.getParameterObject());
			ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, countBoundSql.getParameterObject(), countBoundSql);
			
			stmt = conn.prepareStatement(countBoundSql.getSql());
			parameterHandler.setParameters(stmt);
			rs = stmt.executeQuery();
			if (rs.next()) {
				totalCount = rs.getInt(1);
			}
		} catch (Exception e) {
			throw e;
		} finally {
			closeQuietly(rs, stmt);
		}
		return totalCount;
	}
	
	private static void closeQuietly(ResultSet rs, Statement stmt) {
		try {
			if (rs != null)
				rs.close();
		} catch (Exception e) {
		}
		try {
			if (stmt != null)
				stmt.close();
		} catch (Exception e) {
		}
	}
	
	
	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {
	}

	public void setDialect(String dialect) {
		this.dialect = dialect;
	}
	
}
